using AduSkin.Controls.Tools.Interop;
using System;
using System.Collections.ObjectModel;
using System.Runtime.InteropServices;
using System.Security;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace AduSkin.Controls.Tools.Helper
{
   internal static class IconHelper
   {
      private static Size SmallIconSize;

      private static Size IconSize;

      private static int SystemBitDepth;

      [SecurityCritical, SecuritySafeCritical]
      public static void GetIconHandlesFromImageSource(ImageSource image, out IconHandle largeIconHandle, out IconHandle smallIconHandle)
      {
         EnsureSystemMetrics();
         largeIconHandle = CreateIconHandleFromImageSource(image, IconSize);
         smallIconHandle = CreateIconHandleFromImageSource(image, SmallIconSize);
      }

      [SecurityCritical]
      public static IconHandle CreateIconHandleFromImageSource(ImageSource image, Size size)
      {
         EnsureSystemMetrics();

         var asGoodAsItGets = false;

         var bf = image as BitmapFrame;
         if (bf?.Decoder?.Frames != null)
         {
            bf = GetBestMatch(bf.Decoder.Frames, size);

            asGoodAsItGets = bf.Decoder is IconBitmapDecoder || bf.PixelWidth == (int)size.Width && bf.PixelHeight == (int)size.Height;

            image = bf;
         }

         if (!asGoodAsItGets)
         {
            bf = BitmapFrame.Create(GenerateBitmapSource(image, size));
         }

         return CreateIconHandleFromBitmapFrame(bf);
      }

      [SecurityCritical]
      private static IconHandle CreateIconHandleFromBitmapFrame(BitmapFrame sourceBitmapFrame)
      {
         BitmapSource bitmapSource = sourceBitmapFrame;

         if (bitmapSource.Format != PixelFormats.Bgra32 && bitmapSource.Format != PixelFormats.Pbgra32)
         {
            bitmapSource = new FormatConvertedBitmap(bitmapSource, PixelFormats.Bgra32, null, 0.0);
         }

         var w = bitmapSource.PixelWidth;
         var h = bitmapSource.PixelHeight;
         var bpp = bitmapSource.Format.BitsPerPixel;
         var stride = (bpp * w + 31) / 32 * 4;
         var sizeCopyPixels = stride * h;
         var xor = new byte[sizeCopyPixels];
         bitmapSource.CopyPixels(xor, stride, 0);

         return CreateIconCursor(xor, w, h, 0, 0, true);
      }

      [SecurityCritical]
      internal static IconHandle CreateIconCursor(byte[] colorArray, int width, int height, int xHotspot, int yHotspot, bool isIcon)
      {
         BitmapHandle colorBitmap = null;
         BitmapHandle maskBitmap = null;

         try
         {
            var bi = new InteropValues.BITMAPINFO(width, -height, 32)
            {
               biCompression = InteropValues.BI_RGB
            };

            var bits = IntPtr.Zero;
            colorBitmap = InteropMethods.CreateDIBSection(new HandleRef(null, IntPtr.Zero), ref bi, InteropValues.DIB_RGB_COLORS, ref bits, null, 0);

            if (colorBitmap.IsInvalid || bits == IntPtr.Zero)
            {
               return IconHandle.GetInvalidIcon();
            }

            Marshal.Copy(colorArray, 0, bits, colorArray.Length);
            var maskArray = GenerateMaskArray(width, height, colorArray);

            maskBitmap = InteropMethods.CreateBitmap(width, height, 1, 1, maskArray);
            if (maskBitmap.IsInvalid)
            {
               return IconHandle.GetInvalidIcon();
            }

            var iconInfo = new InteropValues.ICONINFO
            {
               fIcon = isIcon,
               xHotspot = xHotspot,
               yHotspot = yHotspot,
               hbmMask = maskBitmap,
               hbmColor = colorBitmap
            };

            return InteropMethods.CreateIconIndirect(iconInfo);
         }
         finally
         {
            colorBitmap?.Dispose();
            maskBitmap?.Dispose();
         }
      }

      private static byte[] GenerateMaskArray(int width, int height, byte[] colorArray)
      {
         var nCount = width * height;
         var bytesPerScanLine = AlignToBytes(width, 2) / 8;
         var bitsMask = new byte[bytesPerScanLine * height];

         for (var i = 0; i < nCount; i++)
         {
            var hPos = i % width;
            var vPos = i / width;
            var byteIndex = hPos / 8;
            var offsetBit = (byte)(0x80 >> (hPos % 8));

            if (colorArray[i * 4 + 3] == 0x00)
            {
               bitsMask[byteIndex + bytesPerScanLine * vPos] |= offsetBit;
            }
            else
            {
               bitsMask[byteIndex + bytesPerScanLine * vPos] &= (byte)~offsetBit;
            }

            if (hPos == width - 1 && width == 8)
            {
               bitsMask[1 + bytesPerScanLine * vPos] = 0xff;
            }
         }

         return bitsMask;
      }

      internal static int AlignToBytes(double original, int nBytesCount)
      {
         var nBitsCount = 8 << (nBytesCount - 1);
         return ((int)Math.Ceiling(original) + (nBitsCount - 1)) / nBitsCount * nBitsCount;
      }

      private static BitmapSource GenerateBitmapSource(ImageSource img, Size renderSize)
      {
         var drawingDimensions = new Rect(0, 0, renderSize.Width, renderSize.Height);

         var renderRatio = renderSize.Width / renderSize.Height;
         var aspectRatio = img.Width / img.Height;

         if (img.Width <= renderSize.Width && img.Height <= renderSize.Height)
         {
            drawingDimensions = new Rect((renderSize.Width - img.Width) / 2, (renderSize.Height - img.Height) / 2, img.Width, img.Height);
         }
         else if (renderRatio > aspectRatio)
         {
            var scaledRenderWidth = (img.Width / img.Height) * renderSize.Width;
            drawingDimensions = new Rect((renderSize.Width - scaledRenderWidth) / 2, 0, scaledRenderWidth, renderSize.Height);
         }
         else if (renderRatio < aspectRatio)
         {
            var scaledRenderHeight = img.Height / img.Width * renderSize.Height;
            drawingDimensions = new Rect(0, (renderSize.Height - scaledRenderHeight) / 2, renderSize.Width, scaledRenderHeight);
         }

         var dv = new DrawingVisual();
         var dc = dv.RenderOpen();
         dc.DrawImage(img, drawingDimensions);
         dc.Close();

         var bmp = new RenderTargetBitmap((int)renderSize.Width, (int)renderSize.Height, 96, 96, PixelFormats.Pbgra32);
         bmp.Render(dv);

         return bmp;
      }

      private static BitmapFrame GetBestMatch(ReadOnlyCollection<BitmapFrame> frames, Size size)
      {
         var bestScore = int.MaxValue;
         var bestBpp = 0;
         var bestIndex = 0;

         var isBitmapIconDecoder = frames[0].Decoder is IconBitmapDecoder;

         for (var i = 0; i < frames.Count && bestScore != 0; ++i)
         {
            var currentIconBitDepth = isBitmapIconDecoder ? frames[i].Thumbnail.Format.BitsPerPixel : frames[i].Format.BitsPerPixel;

            if (currentIconBitDepth == 0)
            {
               currentIconBitDepth = 8;
            }

            var score = MatchImage(frames[i], size, currentIconBitDepth);
            if (score < bestScore)
            {
               bestIndex = i;
               bestBpp = currentIconBitDepth;
               bestScore = score;
            }
            else if (score == bestScore)
            {
               if (bestBpp < currentIconBitDepth)
               {
                  bestIndex = i;
                  bestBpp = currentIconBitDepth;
               }
            }
         }

         return frames[bestIndex];
      }

      private static int MatchImage(BitmapFrame frame, Size size, int bpp)
      {
         var score = 2 * MyAbs(bpp, SystemBitDepth, false) +
                     MyAbs(frame.PixelWidth, (int)size.Width, true) +
                     MyAbs(frame.PixelHeight, (int)size.Height, true);

         return score;
      }

      private static int MyAbs(int valueHave, int valueWant, bool fPunish)
      {
         var diff = (valueHave - valueWant);

         if (diff < 0)
         {
            diff = (fPunish ? -2 : -1) * diff;
         }

         return diff;
      }

      [SecurityCritical, SecuritySafeCritical]
      private static void EnsureSystemMetrics()
      {
         if (SystemBitDepth == 0)
         {
            var hdcDesktop = new HandleRef(null, InteropMethods.GetDC(new HandleRef()));
            try
            {
               var sysBitDepth = InteropMethods.GetDeviceCaps(hdcDesktop, InteropValues.BITSPIXEL);
               sysBitDepth *= InteropMethods.GetDeviceCaps(hdcDesktop, InteropValues.PLANES);

               if (sysBitDepth == 8)
               {
                  sysBitDepth = 4;
               }

               var cxSmallIcon = InteropMethods.GetSystemMetrics(InteropValues.SM.CXSMICON);
               var cySmallIcon = InteropMethods.GetSystemMetrics(InteropValues.SM.CYSMICON);
               var cxIcon = InteropMethods.GetSystemMetrics(InteropValues.SM.CXICON);
               var cyIcon = InteropMethods.GetSystemMetrics(InteropValues.SM.CYICON);

               SmallIconSize = new Size(cxSmallIcon, cySmallIcon);
               IconSize = new Size(cxIcon, cyIcon);
               SystemBitDepth = sysBitDepth;
            }
            finally
            {
               InteropMethods.ReleaseDC(new HandleRef(), hdcDesktop);
            }
         }
      }

      [SecurityCritical, SecuritySafeCritical]
      public static void GetDefaultIconHandles(out IconHandle largeIconHandle, out IconHandle smallIconHandle)
      {
         largeIconHandle = null;
         smallIconHandle = null;

         SecurityHelper.DemandUIWindowPermission();

         var iconModuleFile = InteropMethods.GetModuleFileName(new HandleRef());
         InteropMethods.ExtractIconEx(iconModuleFile, 0, out largeIconHandle, out smallIconHandle, 1);
      }
   }
}
